#include "SysLib.h"

#include "Tunnel.h"
#include "TunDev.h"
#include "Const.h"
#include "Common.h"
#include "Protocal.h"

PTunnel tunnel_chain_head = NULL;
PTunnel tunnel_chain_tail = NULL;
PTunnel tunnel_list_by_ip_id[260];
PTunnelMeta tunnel_meta_list_by_ip_id[260];

void tunnel_module_init() {
    if (tunnel_chain_head == NULL) {
        zmalloc(&tunnel_chain_head, sizeof(Tunnel));
        tunnel_chain_tail = tunnel_chain_head;
        int i;
        for (i=0; i<260; i++) {
            PTunnelMeta meta;
            zmalloc(&meta, sizeof(TunnelMeta));
            tunnel_meta_list_by_ip_id[i] = meta;
        }
    }
}

PTunnel tunnel_new(int read_buffer, int write_buffer) {
    PTunnel ret = NULL;
    zmalloc(&ret, sizeof(Tunnel));
    ret->read_buffer = buffer_new(read_buffer);
    ret->write_buffer = buffer_new(write_buffer);
    ret->status = -1;
    ret->create_time = ret->last_to_net = ret->last_from_net = time(0);
    ret->heartbeat = 15;
    ret->timeout = 50;
    tunnel_chain_tail->next = ret;
    ret->pre = tunnel_chain_tail;

    tunnel_chain_tail = ret;
    return ret;
}

void tunnel_delete(PTunnel tun) {
    tunnel_close(tun, true);

    if (tun->read_buffer) {
        log_debug("Deleting read buffer, from: %d, to: %d", tun->read_buffer->from, tun->read_buffer->to);
        buffer_delete(tun->read_buffer);
        tun->read_buffer = NULL;
    }
    if (tun->write_buffer) {
        log_debug("Deleting write buffer, from: %d, to: %d", tun->write_buffer->from, tun->write_buffer->to);
        buffer_delete(tun->write_buffer);
        tun->write_buffer = NULL;
    }

    if (tun->socket != NULL) {
        socket_close(tun->socket);
        socket_delete(tun->socket);
        tun->socket = NULL;
    }

    log_info("Delete tunnel, duplicated: %s, username: %s, client_ip: %s",
            tun->duplicated ? "true" : "false",
            tun->username, tun->client_ip);
    if (tun->username) free(tun->username);
    if (tun->client_ip) free(tun->client_ip);
    if (tun->server_ip) free(tun->server_ip);
    
    if (tun->next) {
        tun->next->pre = tun->pre;
    } else {
        tunnel_chain_tail = tun->pre;
    }
    if (tun->pre) {
        tun->pre->next = tun->next;
    }

    free(tun);
}

void tunnel_close(PTunnel tun, bool force) {
    if (tun->status != TSTATUS_CLOSING) {
        log_debug("Closing tunnel, duplicated: %s, username: %s, client_ip: %s",
                tun->duplicated ? "true" : "false",
                tun->username, tun->client_ip);
    }
    if (tun->client_ip_num[3] && tunnel_list_by_ip_id[tun->client_ip_num[3]] == tun) {
        tunnel_list_by_ip_id[tun->client_ip_num[3]] = NULL;
        tunnel_meta_list_by_ip_id[tun->client_ip_num[3]]->connected = false;
    }
    if (force) {
        buffer_clear(tun->write_buffer);
    } else {
        if (tun->status == TSTATUS_ESTABLISH) {
            log_debug("Sending close signale");
            encode_data(NULL, PTYPE_END, tun->write_buffer);
        }
    }
    tun->status = TSTATUS_CLOSING;
}

void tunnel_establish(PTunnel tun, char *client_ip, char *server_ip) {
    tun->client_ip = string_alloc(client_ip);
    tun->server_ip = string_alloc(server_ip);
    zmalloc(&tun->client_ip_num, sizeof(unsigned char) * 4);
    ip_str_to_num(client_ip, 4, tun->client_ip_num);

    PTunnel old_tunnel = tunnel_list_by_ip_id[tun->client_ip_num[3]];
    if (old_tunnel) {
        old_tunnel->duplicated = true;
    }

    tunnel_list_by_ip_id[tun->client_ip_num[3]] = tun;

    tun->status = TSTATUS_ESTABLISH;
    PTunnelMeta meta = tunnel_meta_list_by_ip_id[tun->client_ip_num[3]];
    strcpy(meta->username, tun->username);
    meta->auth_seed = tun->auth_seed;
    meta->connected = true;
    tun->connect_time = time(0);
}


PTunnel tunnel_get_by_ip_id(int ip_id) {
    return tunnel_list_by_ip_id[ip_id];
}

PTunnel tunnel_get_chain_head() {
    return tunnel_chain_head;
}

bool tunnel_connect(PTunnel tun, char *host, int port) {
    tun->socket = socket_connect(host, port, false);
    if (!tun->socket) {
        return false;
    }
    tun->status = TSTATUS_CONNECTING;
    return true;
}

PTunnel tunnel_accept(int server_socket, int write_buffer_size) {
    int net_fd;
    struct sockaddr_in client_ip;
    int addr_len = sizeof(client_ip);
    if ((net_fd = accept(server_socket, (struct sockaddr*)&client_ip, (socklen_t *)&addr_len)) < 0) {
        log_error("accept socket error");
        return NULL;
    }
    log_info("Accept new connection from %s:%d", inet_ntoa(client_ip.sin_addr), client_ip.sin_port);

    if (fcntl(net_fd, F_SETFL, fcntl(net_fd, F_GETFL) | O_NONBLOCK) < 0) {
        log_error("Error setting the socket in nonblocking mode");
        close(net_fd);
        return NULL;
    }

    int flag = 1;
    if (-1 == setsockopt(net_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag))) {
        log_error("Error setting tcp no delay");
        close(net_fd);
        return NULL;
    }

    PTunnel tunnel = tunnel_new(2048, write_buffer_size);
    tunnel->status = TSTATUS_AUTH_BEGIN;
    tunnel->socket = socket_new();
    tunnel->socket->fd = net_fd;
    return tunnel;
}


bool tunnel_is_readable_status(PTunnel tun) {
    if (tun->status >= TSTATUS_AUTH_BEGIN && tun->status <= TSTATUS_ESTABLISH) {
        return true;
    }
    return false;
}

bool tunnel_is_writeable_status(PTunnel tun) {
    switch (tun->status) {
    case TSTATUS_CONNECTING:
        return true;
    case TSTATUS_INIT:
        return false;
    }
    if (buffer_readable(tun->write_buffer)) {
        return true;
    } else {
        return false;
    }
}


PTunnelMeta tunnel_get_meta_by_ip_id(int ip_id) {
    return tunnel_meta_list_by_ip_id[ip_id];
}

bool tunnel_check_nio_connect(PTunnel tunnel) {
    int err = 0;
    socklen_t errlen = sizeof(err);
    if (getsockopt(tunnel->socket->fd, SOL_SOCKET, SO_ERROR, &err, &errlen) != 0) {
        log_error("getsockopt return error");
    } else if (err != 0) {
        log_error("SO_ERROR return %d after connect", err);
    } else {
        return true;
    }
    return false;
}
